﻿/*
Copyright (c) 2007-2011 William POTTIER - AllProgrammic

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of the Software, and to permit persons to whom the Software is furnished to do so, 
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using NetAccess.Common;
using NetAccess.Common.Exceptions;
using NetAccess.Message;

namespace NetAccess.Server
{
    /// <summary>
    /// Server layer base class
    /// </summary>
    /// <typeparam name="T">BaseServerConnexion implementation</typeparam>
    public abstract partial class ServerLayer<T> : BaseServerLayer
        where T : BaseServerConnexion
    {
        #region Fields
        /// <summary>
        /// Dictionnary of client connexions
        /// </summary>
        private Dictionary<int, T> _connexions;

        private ReadOnlyDictionary<int, T> _clients;

        /// <summary>
        /// Liste des clients
        /// </summary>
        private Dictionary<int, List<BaseServerConnexion>> _rooms;
        #endregion

        #region Properties
        public override ServerConfiguration Configuration { get; protected set; }

        /// <summary>
        /// The current state of the server
        /// </summary>
        public ServerLayerState State { get; private set; }

        /// <summary>
        /// Client List
        /// </summary>
        /// <remarks>Dictionary is read only.</remarks>
        protected ReadOnlyDictionary<int, T> Clients
        {
            get { return _clients ?? (_clients = new ReadOnlyDictionary<int, T>(_connexions)); }
        }
        #endregion

        protected ServerLayer()
        {
            State = ServerLayerState.Stopped;
        }

        #region Starting
        protected void Start(ServerConfiguration configuration)
        {
            // Check if ready to start
            if (State != ServerLayerState.Stopped)
                return;

            // Configuration
            Configuration = (ServerConfiguration)configuration.Clone();
            Configuration.LockConfiguration();

            // Initialization
            _connexions = new Dictionary<int, T>();
            _rooms = new Dictionary<int, List<BaseServerConnexion>>();

            State = ServerLayerState.Starting;

            // TCP Service
            if (Configuration.IsEnableTCP && Configuration.TCPServiceEndPoint != null)
            {
                StartTCPService();
            }

            // UDP Service
            if (Configuration.IsEnableDirectUDP && Configuration.UDPServiceEndPoint != null)
            {
                StartUDPService();
            }

            // Service Discovery
            if (Configuration.IsEnableServiceDiscovery && Configuration.ServiceDiscoveryEndPoint != null)
            {
                StartServiceDiscovery();
            }

            State = ServerLayerState.Started;
            OnStarted();
        }

        /// <summary>
        /// Call when server is started
        /// </summary>
        public virtual void OnStarted() { }
        #endregion

        #region Arrêt
        /// <summary>
        /// Stop the server
        /// </summary>
        public void Stop()
        {
            if (State != ServerLayerState.Started)
                return;

            State = ServerLayerState.Stopping;

            // Stop service discovery
            StopServiceDiscovery();

            // Stop UDP Service
            StopUDPService();

            // Stop TCP Service
            StopTCPService();
            
            // Disconnect clients
            foreach (var connexion in _connexions.Values)
            {
                connexion.Disconnect();
            }
            _connexions.Clear();

            State = ServerLayerState.Stopped;
            OnStopped();
        }

        /// <summary>
        /// Call when server is stopped
        /// </summary>
        public virtual void OnStopped() { }
        #endregion

        #region Customer Management
        internal override void ClientConnect(BaseServerConnexion connexion)
        {
            var c = connexion as T;
            if (c == null) return;

            _connexions.Add(c.Id, c);
            OnClientConnected(c);
        }

        protected virtual void OnClientConnected(T client) { }

        internal override void OnClientDisconnected(BaseServerConnexion client)
        {
            if (State != ServerLayerState.Stopping)
                _connexions.Remove(client.Id);

            OnClientDisconnected((T)client);
        }

        protected virtual void OnClientDisconnected(T client) { }

        #region Linq
        // ReSharper disable LoopCanBeConvertedToQuery
        protected IEnumerable<T> FindClientWhere(Func <T, Boolean> predicate)
        {
            foreach (var obj in _connexions.Values)
            {
                if (predicate(obj))
                {
                    yield return obj;
                }
            }
        }
        // ReSharper restore LoopCanBeConvertedToQuery
        #endregion
        #endregion

        #region Internal Sending
        /// <summary>
        /// Broadcast a message to all client on the server except this one
        /// </summary>
        /// <param name="exceptConnexion"></param>
        /// <param name="message"></param>
        /// <param name="crypt"></param>
        internal override void InternalBroadcast(InternalMessage message, bool crypt = false, BaseServerConnexion exceptConnexion = null)
        {
            _connexions.Values
                .Where(connexion => exceptConnexion == null || connexion.Id != exceptConnexion.Id)
                .ForEach(connexion => connexion.InternalSend(message, false, crypt));
        }

        internal override void InternalBroadcastRoom(int roomIdentifier, InternalMessage message, bool crypt = false, BaseServerConnexion exceptConnexion = null)
        {
            if (!_rooms.ContainsKey(roomIdentifier))
                throw new NetAccessException(String.Format("No room with id \"{0}\" found.", roomIdentifier), new ArgumentException("Invalid room identifier", "roomIdentifier"));

            if (!_rooms[roomIdentifier].Contains(exceptConnexion))
                throw new NetAccessException(String.Format("Connexion not present in room {0} and cannot send to it", roomIdentifier));

            _rooms[roomIdentifier]
                .Where(connexion => exceptConnexion == null || connexion.Id != exceptConnexion.Id)
                .ForEach(connexion => connexion.InternalSend(message, false, crypt));
        }


        #region Rooms Management
        internal override void InternalCreateRoom(int roomIdentifier)
        {
            if (!_rooms.ContainsKey(roomIdentifier))
                throw new NetAccessException(String.Format("Room with id \"{0}\" allready exist.", roomIdentifier), new ArgumentException("Invalid room identifier", "roomIdentifier"));

            _rooms.Add(roomIdentifier, new List<BaseServerConnexion>());
        }

        internal override void InternalJoinRoom(int roomIdentifier, BaseServerConnexion connexion)
        {
            if (!_rooms.ContainsKey(roomIdentifier))
                throw new NetAccessException(String.Format("No room with id \"{0}\" found.", roomIdentifier), new ArgumentException("Invalid room identifier", "roomIdentifier"));

            if(_rooms[roomIdentifier].Contains(connexion))
                throw new NetAccessException(String.Format("Connexion allready exist in room {0}", roomIdentifier), new ArgumentException("Connexion allready in room", "connexion"));

            _rooms[roomIdentifier].Add(connexion);
        }

        internal override void InternalRemoveFromRoom(int roomIdentifier, BaseServerConnexion connexion)
        {
            if (!_rooms.ContainsKey(roomIdentifier))
                throw new NetAccessException(String.Format("No room with id \"{0}\" found.", roomIdentifier), new ArgumentException("Invalid room identifier", "roomIdentifier"));

            if (!_rooms[roomIdentifier].Contains(connexion))
                throw new NetAccessException(String.Format("Connexion does not exist in room {0}", roomIdentifier), new ArgumentException("Connexion does not exist in room", "connexion"));

            _rooms[roomIdentifier].Remove(connexion);
        }

        internal override void InternalDeleteRoom(int roomIdentifier)
        {
            if (!_rooms.ContainsKey(roomIdentifier))
                throw new NetAccessException(String.Format("No room with id \"{0}\" found.", roomIdentifier), new ArgumentException("Invalid room identifier", "roomIdentifier"));

            _rooms.Remove(roomIdentifier);
        }
        #endregion
        #endregion
    }
}
